/***************************************************************
 *
 * (c) 2008 Markus Dittrich 
 *
 * This program is free software; you can redistribute it 
 * and/or modify it under the terms of the GNU General Public 
 * License Version 3 as published by the Free Software Foundation. 
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License Version 3 for more details.
 *
 * You should have received a copy of the GNU General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, 
 * Boston, MA 02111-1307, USA.
 *
 ****************************************************************/

/** boost includes */
#include <boost/shared_ptr.hpp>

/** Qt include */
#include <QMutexLocker>

/** STL includes */
#include <vector>
#include <fstream>
#include <algorithm>

/** local includes */
#include "io.h"
#include "utilities.h"

/** pull in some things from std namespace */
using std::map;
using std::vector;
using std::string;



/*****************************************************************
 *
 * class IO is responsible for handling all I/O;
 * file parsing is done in a separate thread to keep the main
 * GUI responsive
 *
 *****************************************************************/

//----------------------------------------------------------------
// constructor
//----------------------------------------------------------------
IO::IO(QObject* theParent, DataMap& dataContainer)
  :
    QThread(theParent),
    dataContainer_(dataContainer)
{
  connect(this,SIGNAL(currently_processed_file(QString)),
      theParent,SLOT(list_in_status_bar(QString)));
  connect(this,SIGNAL(error_reading_file(QString)),
      theParent,SLOT(signal_file_reading_error(QString)));
}


/****************************************************************
 *
 * PUBLIC FUNCTIONS
 *
 ***************************************************************/

//---------------------------------------------------------------
// read content of all files in filequeue and add data to
// dataContainer 
//---------------------------------------------------------------
void IO::run()
{
  /* make sure this section is only executed one thread
   * at a time */
  QMutexLocker locker(&mutex_);

  /* keep list of files we need to remove from the 
   * filequeue because they failed to load */
  vector<QString> deadFiles; 

  FOREACH(const FileQueueItem& queueItem, fileQueue_)
  {
    /* check if we need to re-read the file, skip otherwise */
    if ( queueItem.second == false )
    {
      continue;
    }

    QString fileName = queueItem.first;
    std::ifstream current_file(fileName.toLatin1().data());
    if ( !current_file ) 
    {
      continue;
    }
    emit currently_processed_file(fileName);

    /* read until we hit the first line of data, i.e. a
     * line that is not empty or a comment starting
     * with a # */
    string line;
    while ( getline(current_file,line) )
    {
      if ( !line.empty() && line[0] != '#' )
      {
        break;
      }
    }

    /* if we end up here with an empty line we
     * probably tried to open an empty file;
     * lets abort in this case */
    if ( line.empty() )
    {
      emit error_reading_file(fileName);
      deadFiles.push_back(fileName);
      continue; // read next queued file
    }

    /* the first data line determines the number of 
     * columns; the success flag indicates if there
     * were any trouble during parsing in which case
     * we terminate reading of the file and let the
     * data object destroy itself */
    bool success = true;
    vector<double> separateLineItems;
    success = string_to_doubles(separateLineItems, line);
    int numColumns = separateLineItems.size();

    /* construct DataObject and append first line */
    DataObjectPtr currentDataObject = 
      DataObjectPtr(new DataObject(numColumns));
    currentDataObject->append(separateLineItems);

    /* start appending; skip empty lines */
    while (getline(current_file,line))
    {
      if ( line.empty() || line[0] == '#' )
      {
        continue;
      }
      
      separateLineItems.clear();
      success &= string_to_doubles(separateLineItems,line,numColumns);
      currentDataObject->append(separateLineItems);
    }

    /* if something went wrong during parsing we stop here and
     * let the main thread know that something went wrong;
     * also remove the file from the file queue */
    if ( !success )
    {
      emit error_reading_file(fileName);
      deadFiles.push_back(fileName);
      continue;   // read next queued file
    }

    dataContainer_[fileName] = currentDataObject;

    /* mark file as read */
    fileQueue_[fileName] = false;
  }

  remove_from_filequeue(deadFiles);
}


//---------------------------------------------------------------
// marks all files in the filequeue as un-read to force 
// re-reading of the data content 
//---------------------------------------------------------------
void IO::mark_filequeue_unread()
{
  FOREACH(const FileQueueItem& queueItem, fileQueue_)
  {
    QString fileName = queueItem.first;
    fileQueue_[fileName] = true;
  }
}
 

//---------------------------------------------------------------
// add file(s) to the file queue if it is a new file and
// mark it/them as to be read
//---------------------------------------------------------------
void IO::append_to_filequeue(const QString& fileName)
{
  QMutexLocker lock(&mutex_);
  if ( fileQueue_.find(fileName) == fileQueue_.end() )
  {
    fileQueue_[fileName] = true;
  }
}

void IO::append_to_filequeue(const vector<QString>& fileNames)
{
  QMutexLocker lock(&mutex_);
  FOREACH(const QString& fileName, fileNames)
  {
    if ( fileQueue_.find(fileName) == fileQueue_.end() )
    {
      fileQueue_[fileName] = true;
    }
  }
}


//---------------------------------------------------------------
// remove file(s) from file queue
//---------------------------------------------------------------
void IO::remove_from_filequeue(const QString& fileName)
{
  fileQueue_.erase(fileName);
}


void IO::remove_from_filequeue(const vector<QString>& fileNames)
{
  FOREACH(const QString& FileName, fileNames)
  {
    remove_from_filequeue(FileName);
  }
}

